/**
 * 
 */
package jvs.vfs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import x.java.io.File;
import x.java.io.FileOutputStream;

/**
 * @author qiangli
 * 
 */
public class FileManager {
	private static final String DRIVER_NAME = "x.java.file.driver";

	private static FileManager man = new FileManager();

	protected PrintStream out = System.out;

	protected Driver driver;

	protected int bufsize;

	protected Properties contentTypes;

	public static FileManager getDefault() {
		return man;
	}

	public FileManager() {
		String className = System.getProperty(DRIVER_NAME);
		if (className == null) {
			driver = new DefaultDriver();
		} else {
			try {
				Class<?> claz = Class.forName(className);
				driver = (Driver) claz.newInstance();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		bufsize = 1024;
		loadContentTypes();
	}

	public FileManager(Driver driver, int bufsize) {
		this.driver = driver;
		this.bufsize = bufsize;
		loadContentTypes();
	}

	private void loadContentTypes() {
		try {
			contentTypes = new Properties();
			x.java.io.File f = new x.java.io.File(
					"/etc/content-types.properties");
			InputStream is = f.getInputStream();
			contentTypes.load(is);
			is.close();
		} catch (IOException e) {
			contentTypes = null;
			e.printStackTrace();
		}
	}

	public Properties getContentTypes() {
		return contentTypes;
	}

	public File createFile(URI uri) throws IOException {
		return (File) driver.createFile(uri);
	}

	public File createFile(File parent, String name) throws IOException {
		return (File) driver.createFile(parent, name);
	}

	public InputStream createInputStream(File file) throws IOException {
		return driver.createInputStream(file);
	}

	public OutputStream createOutputStream(File file) throws IOException {
		return driver.createOutputStream(file);
	}

	public Reader createReader(File file) throws IOException {
		return driver.createReader(file);
	}

	public Writer createWriter(File file) throws IOException {
		return driver.createWriter(file);
	}

	public File createTempFile(String prefix, String suffix) throws IOException {
		return (File) File.createTempFile(prefix, suffix);
	}

	public synchronized File createTempDir(String prefix, String suffix)
			throws IOException {
		long cnt = System.currentTimeMillis();
		while (true) {
			File f = (File) File.createTempFile(prefix + (cnt++), suffix);
			if (f.delete() && f.mkdirs()) {
				return f;
			}
		}
	}

	public void delete(File file) throws IOException {
		if (file.isDirectory()) {
			String[] files = file.list();
			if (files.length > 0) {
				throw new IOException("Not empty: " + file);
			}
		}
		boolean rc = file.delete();
		if (!rc) {
			throw new IOException("Delete: " + file);
		}
	}

	public void copy(File from, File to) throws IOException {
		if (!from.isFile()) {
			throw new IOException("Directory copy not supported: " + from);
		}
		if (to.isDirectory()) {
			to = new File(to, from.getName());
		}
		BufferedInputStream is = createBufferedInputStream(from);
		BufferedOutputStream os = createBufferedOutputStream(to);

		copy(is, os, true);
	}

	public void copy(InputStream in, OutputStream out, boolean close)
			throws IOException {
		int read = -1;
		byte[] b = new byte[bufsize];
		while ((read = in.read(b)) != -1) {
			out.write(b, 0, read);
		}
		if (close) {
			in.close();
			out.close();
		}
	}

	public int count(InputStream in) throws IOException {
		int read = -1;
		byte[] b = new byte[bufsize];
		int cnt = 0;
		while ((read = in.read(b)) != -1) {
			cnt += read;
		}
		return cnt;
	}

	public void touch(File file, long now) {
		file.setLastModified(now);
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (int i = 0; i < files.length; i++) {
				touch(files[i], now);
			}
		}
	}

	public void mkdir(File file) {
		file.mkdirs();
	}

	public void rename(File from, File to) {
		from.renameTo(to);
	}

	public void zip(File file, File zip) throws IOException {
		ZipOutputStream out = new ZipOutputStream(
				createBufferedOutputStream(zip));
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					continue;// Ignore
				}
				InputStream in = createBufferedInputStream(files[i]);
				ZipEntry ze = new ZipEntry(files[i].getPath());
				out.putNextEntry(ze);
				copy(in, out, false);
				in.close();
			}
		} else {
			InputStream in = createBufferedInputStream(file);
			copy(in, out, false);
			in.close();
		}
		out.close();
	}

	public void grep(File file, String regex, PrintStream out)
			throws IOException {
		if (out == null) {
			out = this.out;
		}
		InputStream in = createBufferedInputStream(file);
		Pattern p = Pattern.compile(regex);
		BufferedReader r = new BufferedReader(new InputStreamReader(in),
				bufsize);
		String l;
		for (l = r.readLine(); l != null; l = r.readLine()) {
			Matcher m = p.matcher(l);
			if (m.find()) {
				out.println(l);
			}
		}
	}

	public void cat(File[] files, File out) throws IOException {
		OutputStream os = createBufferedOutputStream(out);
		for (File f : files) {
			InputStream is = createBufferedInputStream(f);
			copy(is, os, true);
		}
		os.close();
	}

	public File[] split(File file, long size, File out) throws IOException {
		long len = file.length();
		int cnt = (int) (len / size) + 1;
		if (cnt == 1) {
			return new File[] { file };
		}
		if (!out.mkdirs()) {
			throw new IOException("Create: " + out);
		}
		File[] files = new File[cnt];
		String name = file.getName();
		InputStream is = createBufferedInputStream(file);
		for (int i = 0; i < cnt; i++) {
			files[i] = createFile(out, name + "." + i);
			OutputStream os = createBufferedOutputStream(files[i]);
			for (int j = 0; j < size; j++) {
				int b = is.read();
				os.write((byte) b);
			}
			os.close();
		}
		is.close();
		return files;
	}

	public void find(File dir, String pattern, List<File> out) {
		Pattern p = Pattern.compile(pattern);
		find(dir, p, out);
	}

	private void find(File dir, Pattern pattern, List<File> out) {
		if (dir.isFile()) {
			if (pattern.matcher(dir.getName()).matches()) {
				if (out != null) {
					out.add(dir);
				} else {
					this.out.println(dir);
				}
			}
			return;
		}
		File[] files = dir.listFiles();
		for (File f : files) {
			find(f, pattern, out);
		}
	}

	public void walk(File dir, FileFilter filter, List<File> out) {
		if (dir.isFile()) {
			if (filter.accept(dir) && out != null) {
				out.add(dir);
			}
			return;
		}
		File[] files = dir.listFiles();
		for (File f : files) {
			walk(f, filter, out);
		}
	}

	public void safeSave(InputStream is, File file) throws IOException {
		File sav = new File(file.getParentFile(), file.getName() + ".sav");
		OutputStream os = new FileOutputStream(sav);
		copy(is, os, false);
		os.close();
		if ((file.exists() && file.delete()) || !file.exists()) {
			sav.renameTo(file);
		}
	}

	protected BufferedInputStream createBufferedInputStream(File file)
			throws IOException {
		return new BufferedInputStream(createInputStream(file), bufsize);
	}

	protected BufferedOutputStream createBufferedOutputStream(File file)
			throws IOException {
		return new BufferedOutputStream(createOutputStream(file), bufsize);
	}
}
